package com.jiangyao.common.utils;

import com.jiangyao.common.constants.ConfigConstants;
import com.jiangyao.common.exception.ServerException;
import com.jiangyao.service.ConfigService;
import lombok.Data;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * FTP客户端
 *
 * @author denggang
 * @version 2018年110月10日 下午8:40:27
 */
@Data
@Configuration
@Service
public class FtpClient {
	private static final Logger LOGGER = LoggerFactory.getLogger(FtpClient.class);
	private static final String PATH_IMAGE = "/img";//图片
	private static final String PATH_FILE = "/file";//文件
	private static final String PATH_AUDIO = "/audio";//音频
	private static final String PATH_VIDEO = "/video";//视频
	private final static DateTimeFormatter FULL_Y = DateTimeFormatter.ofPattern("yyyy");
	private final static DateTimeFormatter FULL_YMD = DateTimeFormatter.ofPattern("yyyyMMdd");
	private final static DateTimeFormatter FULL_ALL = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
	private final static int timeout = 600000;                                  // 超时十分钟
	private final static int BUFFER_SIZE = 2 * 1024;
	private final static long FILE_SIZE_LIMIT_IMG = 10 * 1024 * 1024;
	@Value("${ftp.ip}")
	private String ip;
	@Value("${ftp.port}")
	private Integer port;
	@Value("${ftp.username}")
	private String username;
	@Value("${ftp.password}")
	private String password;


	private FTPClient getInstance() throws Exception {
		FTPClient client = new FTPClient();
		try {
			client.connect(ip, port);
			if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
				if (!client.login(username, password)) {
					client.logout();
					throw new Exception("ftp login failed");
				}
			}
			// 文件类型,默认是ASCII
			client.setFileType(org.apache.commons.net.ftp.FTPClient.BINARY_FILE_TYPE);
			client.setControlEncoding("UTF-8");
			// 设置被动模式
			client.enterLocalPassiveMode();
			client.setConnectTimeout(timeout);
			client.setBufferSize(1024);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
			try {
				client.logout();
			} catch (Exception e2) {
				LOGGER.error(e.getMessage(), e);
			}
			throw e;
		}
		return client;
	}

	/**
	 * 上传图片
	 *
	 * @param file 图片
	 * @return 上传结果路径
	 */
	public String uploadImg(byte[] file, String suffix) {
		InputStream in = null;
		if (file == null || file.length == 0) {
			throw new ServerException(500, "文件异常");
		}
		//图片大小
		if (file.length > FILE_SIZE_LIMIT_IMG) {
			throw new ServerException(500, "文件异常");
		}
		FTPClient client = null;
		try {
			client = getInstance();
			String path;
			in = new ByteArrayInputStream(file);
			try {
				path = createDir(PATH_IMAGE, client);
			} catch (Exception e) {
				//如果创建目录失败就重试一次
				if (e.getMessage().equals("create dir failed")) {
					try {
						path = createDir(PATH_IMAGE, client);
					} catch (Exception e2) {
						LOGGER.error("--- 上传图片失败 - error:{}--", e.getMessage(), e);
						return null;
					}
				} else {
					return null;
				}
			}
			BufferedImage big = ImageIO.read(new ByteArrayInputStream(file));
			String fileName = UUID.randomUUID().toString().replace("-", "") + "_" + big.getWidth() + "_" + big.getHeight() + "_" + file.length + ".0x0." + suffix;
			path = path + "/" + fileName;
			if (client.storeFile(path, in)) {
				return path;
			} else {
				throw new ServerException("上传失败");
			}
		} catch (Exception e) {
			LOGGER.error("--- 上传图片失败---,error:{}-", e.getMessage(), e);
			throw new ServerException("上传图片失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("close inputStream error");
				}
			}
			close(client);
		}
	}


	/**
	 * 上传图片
	 *
	 * @param file 图片文件
	 * @return 上传结果路径
	 */
	public String uploadImg(File file) {
		if (file == null) {
			throw new ServerException("文件为空");
		}
		//图片大小
		if (file.length() > FILE_SIZE_LIMIT_IMG) {
			throw new ServerException("104");
		}
		return uploadImg(file, FilenameUtils.getExtension(file.getName()));
	}


	private String uploadImg(File file, String suffix) {
		FTPClient client = null;
		InputStream in = null;
		try {
			client = getInstance();
			if (file.isFile()) {
				String path;
				try {
					path = createDir(PATH_IMAGE, client);
				} catch (Exception e) {
					//如果创建目录失败就重试一次
					if (e.getMessage().equals("create dir failed")) {
						try {
							path = createDir(PATH_IMAGE, client);
						} catch (Exception e2) {
							LOGGER.error("--- 上传图片失败 -error:{}--", e.getMessage(), e);
							return null;
						}
					} else {
						return null;
					}
				}
				BufferedImage big = ImageIO.read(new FileInputStream(file));
				String wh = "";
				if (big != null) {
					wh = big.getWidth() + "_" + big.getHeight();
				}
				String fileName = UUID.randomUUID().toString().replace("-", "") + "_" + wh + "_" + file.length() + ".0x0." + suffix;
				in = new FileInputStream(file);
				path = path + "/" + fileName;
				if (client.storeFile(path, in)) {
					return path;
				} else {
					throw new ServerException("上传失败");
				}
			} else {
				throw new ServerException("---not file---");
			}
		} catch (Exception e) {
			LOGGER.error("--- 上传图片失败----,error:{}-", e.getMessage(), e);
			throw new ServerException("上传图片失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("close inputStream error");
				}
			}
			close(client);
		}
	}

	/**
	 * 上传视频
	 *
	 * @param file 视频
	 * @return 上传结果路径
	 */
	public String uploadVideo(File file) {
		InputStream in = null;
		FTPClient client = null;
		try {
			if (StringUtils.isBlank(FilenameUtils.getExtension(file.getName()))) {
				throw new ServerException("extension is null");
			}
			if (file.isFile()) {
				client = getInstance();
				String path;
				try {
					path = createDir(PATH_VIDEO, client);
				} catch (Exception e) {
					//如果创建目录失败就重试一次
					if (e.getMessage().equals("create dir failed")) {
						try {
							path = createDir(PATH_VIDEO, client);
						} catch (Exception e2) {
							LOGGER.error("--- 上传视频失败  error:{}--", e.getMessage(), e);
							throw new ServerException("上传视频失败");
						}
					} else {
						return null;
					}
				}
				in = new FileInputStream(file);
				path = path + "/" + UUID.randomUUID().toString().replace("-", "") + format(FULL_ALL) + "." + FilenameUtils.getExtension(file.getName());
				if (client.storeFile(path, in)) {
					return path;
				} else {
					throw new ServerException("上传失败");
				}
			} else {
				throw new ServerException("--not file---");
			}
		} catch (Exception e) {
			LOGGER.error("--- 上传视频失败  error:{}--", e.getMessage(), e);
			throw new ServerException("上传视频失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("close inputStream error");
				}
			}
			close(client);
		}
	}

	/**
	 * 上传视频
	 *
	 * @param file 视频
	 * @return 上传结果路径
	 */
	public String uploadVideo(byte[] file, String suffix) {
		InputStream in = null;
		FTPClient client = null;
		try {
			if (file == null || file.length == 0) {
				throw new ServerException(500, "文件异常");
			}
			client = getInstance();
			String path;
			try {
				path = createDir(PATH_VIDEO, client);
			} catch (Exception e) {
				//如果创建目录失败就重试一次
				if (e.getMessage().equals("create dir failed")) {
					try {
						path = createDir(PATH_VIDEO, client);
					} catch (Exception e2) {
						LOGGER.error("--- 上传视频失败  error:{}--", e.getMessage(), e);
						throw new ServerException("上传视频失败");
					}
				} else {
					return null;
				}
			}
			in = new ByteArrayInputStream(file);
			path = path + "/" + UUID.randomUUID().toString().replace("-", "") + format(FULL_ALL) + "." + suffix;
			if (client.storeFile(path, in)) {
				return path;
			} else {
				throw new ServerException("上传失败");
			}

		} catch (Exception e) {
			LOGGER.error("--- 上传视频失败  error:{}--", e.getMessage(), e);
			throw new ServerException("上传视频失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("close inputStream error");
				}
			}
			close(client);
		}
	}

	/**
	 * 上传音频
	 *
	 * @param file 音频
	 * @return 音频路径
	 */
	public String uploadAudio(File file) {
		String extension = FilenameUtils.getExtension(file.getName());
		if (StringUtils.isBlank(extension)) {
			extension = "mp3";
		}
		if (file.isFile()) {
			try {
				return uploadAudio(new FileInputStream(file), extension);
			} catch (FileNotFoundException e) {
				LOGGER.error("--- 上传音频失败  error:{}--", e.getMessage(), e);
				throw new ServerException("上传音频失败");
			}
		} else {
			throw new ServerException("not file");
		}
	}

	/**
	 * 上传音频
	 *
	 * @param in        音频流
	 * @param extension 音频后缀
	 * @return 路径
	 */
	public String uploadAudio(InputStream in, String extension) {
		FTPClient client = null;
		try {
			if (StringUtils.isBlank(extension)) {
				throw new ServerException("extension is null");
			}
			client = getInstance();
			String path;
			try {
				path = createDir(PATH_AUDIO, client);
			} catch (Exception e) {
				//如果创建目录失败就重试一次
				if (e.getMessage().equals("create dir failed")) {
					try {
						path = createDir(PATH_AUDIO, client);
					} catch (Exception e2) {
						LOGGER.error("--- 上传音频失败  error:{}--", e.getMessage(), e);
						throw new ServerException("上传音频失败");
					}
				} else {
					return null;
				}
			}
			path = path + "/" + UUID.randomUUID().toString().replace("-", "") + format(FULL_ALL) + "." + extension;
			if (client.storeFile(path, in)) {
				return path;
			} else {
				throw new ServerException("上传失败");
			}
		} catch (Exception e) {
			LOGGER.error("--- 上传音频失败  error:{}--", e.getMessage(), e);
			throw new ServerException("上传音频失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("close inputStream error");
				}
			}
			close(client);
		}
	}

	/**
	 * 上传文件(除视频、音频、图片以外的文件才能调用)
	 *
	 * @param in       文件流
	 * @param fileName 文件名称
	 * @return 文件上传结果路径
	 */
	public String uploadFile(InputStream in, String fileName) {
		FTPClient client = null;
		try {
			client = getInstance();
			String path;
			try {
				path = createDir(PATH_FILE, client);
			} catch (Exception e) {
				//如果创建目录失败就重试一次
				if (e.getMessage().equals("create dir failed")) {
					try {
						path = createDir(PATH_FILE, client);
					} catch (Exception e2) {
						LOGGER.error("--- 上传文件失败 -error:{}--", e.getMessage(), e);
						return null;
					}
				} else {
					return null;
				}
			}
			path = path + "/" + UUID.randomUUID().toString().replace("-", "") + format(FULL_ALL) + "." + FilenameUtils.getExtension(fileName);
			if (client.storeFile(path, in)) {
				return path;
			} else {
				throw new ServerException("上传失败");
			}
		} catch (Exception e) {
			LOGGER.error("--- 上传文件失败： {}---,error:{}-", e.getMessage(), e);
			throw new ServerException("上传文件失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("--close inputStream error");
				}
			}
			close(client);
		}
	}

	/**
	 * 上传文件(除视频、音频、图片以外的文件才能调用)
	 *
	 * @param file 文件
	 * @return 文件路径
	 */
	public String uploadFile(File file) {
		InputStream in = null;
		FTPClient client = null;
		try {
			if (file.isFile()) {
				client = getInstance();
				String path;
				try {
					path = createDir(PATH_FILE, client);
				} catch (Exception e) {
					//如果创建目录失败就重试一次
					if (e.getMessage().equals("create dir failed")) {
						try {
							path = createDir(PATH_FILE, client);
						} catch (Exception e2) {
							LOGGER.error("--- 上传文件失败  error:{}--", e.getMessage(), e);
							return null;
						}
					} else {
						return null;
					}
				}
				in = new FileInputStream(file);
				path = path + "/" + UUID.randomUUID().toString().replace("-", "") + format(FULL_ALL) + "." + FilenameUtils.getExtension(file.getName());
				client.storeFile(path, in);
				return path;
			} else {
				throw new ServerException("上传失败,不是文件");
			}
		} catch (Exception e) {
			LOGGER.error("--- 上传文件失败 error:{}--", e.getMessage(), e);
			throw new ServerException("上传文件失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("--close inputStream error");
				}
			}
			close(client);
		}
	}


	/**
	 * 下载文件
	 *
	 * @param downFile 下载文件全名
	 * @param saveFile 保存的文件全名
	 */
	public void download(String downFile, String saveFile) {
		OutputStream out = null;
		FTPClient client = null;
		try {
			client = getInstance();
			File sfile = new File(saveFile);
			File sfolder = sfile.getParentFile();
			if (!sfolder.exists()) {
				if (!sfolder.mkdirs()) {
					LOGGER.error("create dir failed");
				}
			}
			out = new FileOutputStream(sfile);
			client.retrieveFile(downFile, out);
			out.flush();
		} catch (Exception e) {
			LOGGER.error("--- 下载文件失败 ---" + downFile + ".\n" + e.getMessage(), e);
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (Exception e) {
					LOGGER.error("--close outputStream error");
				}
			}
			close(client);
		}
	}

	/**
	 * 下载文件
	 *
	 * @param downFile 下载文件名
	 */
	public byte[] download(String downFile) {
		ByteArrayOutputStream out = null;
		FTPClient client = null;
		try {
			client = getInstance();
			out = new ByteArrayOutputStream();
			client.retrieveFile(downFile, out);
			out.flush();
			return out.toByteArray();
		} catch (Exception e) {
			LOGGER.error("--- 下载文件失败 ---" + downFile + ".\n" + e.getMessage(), e);
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (Exception e) {
					LOGGER.error("--close outputStream error");
				}
			}
			close(client);
		}
		return null;
	}

	/**
	 * 删除文件
	 *
	 * @param file 文件全名
	 */
	public void delFile(String file) {
		FTPClient client = null;
		try {
			client = getInstance();
			client.deleteFile(file);
			LOGGER.warn("---- 删除文件 :{}--", file);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			close(client);
		}
	}

	/**
	 * 重命名文件
	 */
	public void reName(String file, String newFile) {
		FTPClient client = null;
		try {
			client = getInstance();
			client.rename(file, newFile);
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			close(client);
		}
	}

	/**
	 * 生成文件夹
	 *
	 * @param rootDir 根文件夹目录
	 * @throws Exception 异常
	 */
	public String createDir(String rootDir, FTPClient client) throws Exception {
		String path = rootDir;
		// 目录例子：  /img/2018/20181010
		makeDir(path, client);
		path = path + "/" + format(FULL_Y);
		makeDir(path, client);
		path = path + "/" + format(FULL_YMD);
		makeDir(path, client);
		return path;
	}

	public void makeDir(String path, FTPClient client) throws Exception {
		try {
			if (!client.changeWorkingDirectory(path)) {
				if (!client.makeDirectory(path)) {
					//创建目录失败就报错
					throw new Exception("create dir failed");
				}
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
			throw e;
		}
	}

	public static String format(DateTimeFormatter df) {
		return new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(df);
	}

	/**
	 * 关闭channel和session
	 */
	public void close(FTPClient client) {
		if (client != null) {
			try {
				client.disconnect();
			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 从网络上下载到客服端
	 *
	 * @param response response
	 * @param urlStr   全路径
	 * @param fileName 文件名称
	 */
	public void downloadByUrl(HttpServletResponse response, String urlStr, String fileName) throws IOException {
		InputStream in = null;
		OutputStream tempOS = null;
		HttpURLConnection conn = null;
		try {
			URL _url = new URL(urlStr);
			conn = (HttpURLConnection) _url.openConnection();
			conn.setConnectTimeout(300000);
			conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36");
			// 得到输入流
			in = conn.getInputStream();
			response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("gb2312"), "ISO8859-1"));
			tempOS = response.getOutputStream();
			byte[] b = new byte[1024];
			int hasRead;
			while ((hasRead = in.read(b)) != -1) {
				tempOS.write(b, 0, hasRead);
			}
			tempOS.flush();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("--close inputStream error");
				}
			}
			if (tempOS != null) {
				try {
					tempOS.close();
				} catch (Exception e) {
					LOGGER.error("--close outputStream error");
				}
			}
			if (conn != null) {
				try {
					conn.disconnect();
				} catch (Exception e) {
					LOGGER.error("--close connection error");
				}
			}
		}
	}

	/**
	 * 从网络上下载到客服端(压缩包)
	 *
	 * @param response    response
	 * @param fileUrls    全路径
	 * @param zipFileName 文件名称
	 */
	public void downloadByUrlForzip(HttpServletResponse response, List<String> fileUrls, String zipFileName) throws IOException {
		ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
		response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "utf-8"));
		try {
			for (int i = 0; i < fileUrls.size(); i++) {
				String fileUrl = fileUrls.get(i);
				int index = fileUrl.lastIndexOf(".");
				String filePreFix = fileUrl.substring(index + 1);//文件后缀
				InputStream in = getInputStreamByUrl(fileUrl);
				if (in == null) {
					continue;
				}
				String fileName = System.currentTimeMillis() + i + 1 + "." + filePreFix;
				byte[] buf = new byte[BUFFER_SIZE];
				zos.putNextEntry(new ZipEntry(fileName));
				zos.setLevel(Deflater.BEST_COMPRESSION);
				int len;
				while ((len = in.read(buf)) != -1) {
					zos.write(buf, 0, len);
				}
				zos.closeEntry();
				in.close();
			}
		} catch (Exception e) {
			throw new RuntimeException("zip error from ZipUtils", e);
		} finally {
			try {
				zos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public InputStream getInputStreamByUrl(String urlStr) {
		InputStream in = null;
		HttpURLConnection conn;
		URL _url;
		try {
			_url = new URL(urlStr);
			conn = (HttpURLConnection) _url.openConnection();
			conn.setConnectTimeout(3000);
			// 得到输入流
			in = conn.getInputStream();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return in;
	}

	/**
	 * 保存远程图片到FTP
	 *
	 * @param remoteImageUrl 图片地址
	 * @return 保存结果路径
	 */
	public String saveRemoteImage(String remoteImageUrl) {
		if (StringUtils.isEmpty(remoteImageUrl)) {
			throw new ServerException("远程图片地址为空");
		}
		FTPClient client = null;
		InputStream in = null;
		try {
			client = getInstance();
			String path;
			try {
				path = createDir(PATH_IMAGE, client);
			} catch (Exception e) {
				//如果创建目录失败就重试一次
				if (e.getMessage().equals("create dir failed")) {
					try {
						path = createDir(PATH_IMAGE, client);
					} catch (Exception e2) {
						LOGGER.error("--- 保存远程图片到FTP失败 - error:{}--", e.getMessage(), e);
						return null;
					}
				} else {
					return null;
				}
			}
			// 格式，后缀
			String suffix = remoteImageUrl.split("\\.")[remoteImageUrl.split("\\.").length - 1];
			String fileName = UUID.randomUUID().toString().replace("-", "") + ".0x0." + suffix;
			path = path + "/" + fileName;
			in = new URL(remoteImageUrl).openStream();
			if (client.storeFile(path, in)) {
				return path;
			} else {
				throw new ServerException("上传失败");
			}
		} catch (Exception e) {
			LOGGER.error("--- 保存远程图片到FTP失败---,error:{}-", e.getMessage(), e);
			throw new ServerException("保存远程图片到FTP失败");
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
					LOGGER.error("close inputStream error");
				}
			}
			close(client);
		}
	}

	/**
	 * 获取ftp访问地址（绝对路径）
	 *
	 * @param path 文件路径
	 * @return 结果
	 */
	public static String fullFtpUrl(String path) {
		if (StringUtils.isBlank(path)) {
			return path;
		}
		if (path.contains("http") || path.contains("HTTP")) {
			return path;
		}
		ConfigService configService = SpringBeanFactoryUtils.getBean(ConfigService.class);
		return configService.getValueFromCache(ConfigConstants.FTP_URL) + path;
	}

	public static void main(String[] args) {
		try {
			FtpClient client = new FtpClient();
			client.setPort(21);
			client.setIp("122.112.217.37");
			client.setPassword("MYLMCAkjxD6S3Nps");
			client.setUsername("root");

			//测试上传
			long startTime = System.currentTimeMillis();
//			System.out.println("==" + client.uploadImg(new File("C:\\Users\\Administrator\\Desktop\\ftp\\1 (1).jpg")));
			System.out.println("==" + client.uploadVideo(new File("C:\\Users\\Administrator\\Desktop\\ftp\\13.mp4")));
			System.out.println("use time: " + (System.currentTimeMillis() - startTime));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
